home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / gnu / texinfo.lha / texinfo-3.1 / info / indices.c < prev    next >
C/C++ Source or Header  |  1993-02-08  |  18KB  |  668 lines

  1. /* indices.c -- Commands for dealing with an Info file Index. */
  2.  
  3. /* This file is part of GNU Info, a program for reading online documentation
  4.    stored in Info format.
  5.  
  6.    Copyright (C) 1993 Free Software Foundation, Inc.
  7.  
  8.    This program is free software; you can redistribute it and/or modify
  9.    it under the terms of the GNU General Public License as published by
  10.    the Free Software Foundation; either version 2, or (at your option)
  11.    any later version.
  12.  
  13.    This program is distributed in the hope that it will be useful,
  14.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16.    GNU General Public License for more details.
  17.  
  18.    You should have received a copy of the GNU General Public License
  19.    along with this program; if not, write to the Free Software
  20.    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21.  
  22.    Written by Brian Fox (bfox@ai.mit.edu). */
  23.  
  24. #include "info.h"
  25. #include "indices.h"
  26.  
  27. /* User-visible variable controls the output of info-index-next. */
  28. int show_index_match = 1;
  29.  
  30. /* In the Info sense, an index is a menu.  This variable holds the last
  31.    parsed index. */
  32. static REFERENCE **index_index = (REFERENCE **)NULL;
  33.  
  34. /* The offset of the most recently selected index element. */
  35. static int index_offset = 0;
  36.  
  37. /* Variable which holds the last string searched for. */
  38. static char *index_search = (char *)NULL;
  39.  
  40. /* A couple of "globals" describing where the initial index was found. */
  41. static char *initial_index_filename = (char *)NULL;
  42. static char *initial_index_nodename = (char *)NULL;
  43.  
  44. /* A structure associating index names with index offset ranges. */
  45. typedef struct {
  46.   char *name;            /* The nodename of this index. */
  47.   int first;            /* The index in our list of the first entry. */
  48.   int last;            /* The index in our list of the last entry. */
  49. } INDEX_NAME_ASSOC;
  50.  
  51. /* An array associating index nodenames with index offset ranges. */
  52. static INDEX_NAME_ASSOC **index_nodenames = (INDEX_NAME_ASSOC **)NULL;
  53. static int index_nodenames_index = 0;
  54. static int index_nodenames_slots = 0;
  55.  
  56. /* Add the name of NODE, and the range of the associated index elements
  57.    (passed in ARRAY) to index_nodenames. */
  58. static void
  59. add_index_to_index_nodenames (array, node)
  60.      REFERENCE **array;
  61.      NODE *node;
  62. {
  63.   register int i, last;
  64.   INDEX_NAME_ASSOC *assoc;
  65.  
  66.   for (last = 0; array[last]; last++);
  67.   assoc = (INDEX_NAME_ASSOC *)xmalloc (sizeof (INDEX_NAME_ASSOC));
  68.   assoc->name = savestring (node->nodename);
  69.  
  70.   if (!index_nodenames_index)
  71.     {
  72.       assoc->first = 0;
  73.       assoc->last = last;
  74.     }
  75.   else
  76.     {
  77.       for (i = 0; index_nodenames[i + 1]; i++);
  78.       assoc->first = 1 + index_nodenames[i]->last;
  79.       assoc->last = assoc->first + last;
  80.     }
  81.   add_pointer_to_array
  82.     (assoc, index_nodenames_index, index_nodenames, index_nodenames_slots,
  83.      10, INDEX_NAME_ASSOC *);
  84. }
  85.  
  86. /* Find and return the indices of WINDOW's file.  The indices are defined
  87.    as the first node in the file containing the word "Index" and any
  88.    immediately following nodes whose names also contain "Index".  All such
  89.    indices are concatenated and the result returned.  If WINDOW's info file
  90.    doesn't have any indices, a NULL pointer is returned. */
  91. REFERENCE **
  92. info_indices_of_window (window)
  93.      WINDOW *window;
  94. {
  95.   FILE_BUFFER *fb;
  96.  
  97.   fb = file_buffer_of_window (window);
  98.  
  99.   return (info_indices_of_file_buffer (fb));
  100. }
  101.  
  102. REFERENCE **
  103. info_indices_of_file_buffer (file_buffer)
  104.      FILE_BUFFER *file_buffer;
  105. {
  106.   register int i;
  107.   REFERENCE **result = (REFERENCE **)NULL;
  108.  
  109.   /* No file buffer, no indices. */
  110.   if (!file_buffer)
  111.     return ((REFERENCE **)NULL);
  112.  
  113.   /* Reset globals describing where the index was found. */
  114.   maybe_free (initial_index_filename);
  115.   maybe_free (initial_index_nodename);
  116.   initial_index_filename = (char *)NULL;
  117.   initial_index_nodename = (char *)NULL;
  118.  
  119.   if (index_nodenames)
  120.     {
  121.       for (i = 0; index_nodenames[i]; i++)
  122.     {
  123.       free (index_nodenames[i]->name);
  124.       free (index_nodenames[i]);
  125.     }
  126.  
  127.       index_nodenames_index = 0;
  128.       index_nodenames[0] = (INDEX_NAME_ASSOC *)NULL;
  129.     }
  130.  
  131.   /* Grovel the names of the nodes found in this file. */
  132.   if (file_buffer->tags)
  133.     {
  134.       TAG *tag;
  135.  
  136.       for (i = 0; tag = file_buffer->tags[i]; i++)
  137.     {
  138.       if (string_in_line ("Index", tag->nodename) != -1)
  139.         {
  140.           NODE *node;
  141.           REFERENCE **menu;
  142.  
  143.           /* Found one.  Get its menu. */
  144.           node = info_get_node (tag->filename, tag->nodename);
  145.           if (!node)
  146.         continue;
  147.  
  148.           /* Remember the filename and nodename of this index. */
  149.           initial_index_filename = savestring (file_buffer->filename);
  150.           initial_index_nodename = savestring (tag->nodename);
  151.  
  152.           menu = info_menu_of_node (node);
  153.  
  154.           /* If we have a menu, add this index's nodename and range
  155.          to our list of index_nodenames. */
  156.           if (menu)
  157.         {
  158.           add_index_to_index_nodenames (menu, node);
  159.  
  160.           /* Concatenate the references found so far. */
  161.           result = info_concatenate_references (result, menu);
  162.         }
  163.           free (node);
  164.         }
  165.     }
  166.     }
  167.  
  168.   /* If there is a result, clean it up so that every entry has a filename. */
  169.   for (i = 0; result && result[i]; i++)
  170.     if (!result[i]->filename)
  171.       result[i]->filename = savestring (file_buffer->filename);
  172.  
  173.   return (result);
  174. }
  175.  
  176. DECLARE_INFO_COMMAND (info_index_search,
  177.    "Look up a string in the index for this file")
  178. {
  179.   FILE_BUFFER *fb;
  180.   char *line;
  181.  
  182.   /* Reset the index offset, since this is not the info-index-next command. */
  183.   index_offset = 0;
  184.  
  185.   /* The user is selecting a new search string, so flush the old one. */
  186.   maybe_free (index_search);
  187.   index_search = (char *)NULL;
  188.  
  189.   /* If this window's file is not the same as the one that we last built an
  190.      index for, build and remember an index now. */
  191.   fb = file_buffer_of_window (window);
  192.   if (!initial_index_filename ||
  193.       (strcmp (initial_index_filename, fb->filename) != 0))
  194.     {
  195.       info_free_references (index_index);
  196.       window_message_in_echo_area ("Finding index entries...");
  197.       index_index = info_indices_of_file_buffer (fb);
  198.     }
  199.  
  200.   /* If there is no index, quit now. */
  201.   if (!index_index)
  202.     {
  203.       info_error ("No indices found.");
  204.       return;
  205.     }
  206.  
  207.   /* Okay, there is an index.  Let the user select one of the members of it. */
  208.   line =
  209.     info_read_maybe_completing (window, "Index entry: ", index_index);
  210.  
  211.   window = active_window;
  212.  
  213.   /* User aborted? */
  214.   if (!line)
  215.     {
  216.       info_abort_key (active_window, 1, 0);
  217.       return;
  218.     }
  219.  
  220.   /* Empty line means move to the Index node. */
  221.   if (!*line)
  222.     {
  223.       free (line);
  224.  
  225.       if (initial_index_filename && initial_index_nodename)
  226.     {
  227.       NODE *node;
  228.  
  229.       node =
  230.         info_get_node (initial_index_filename, initial_index_nodename);
  231.       set_remembered_pagetop_and_point (window);
  232.       window_set_node_of_window (window, node);
  233.       remember_window_and_node (window, node);
  234.       window_clear_echo_area ();
  235.       return;
  236.     }
  237.     }
  238.  
  239.   /* The user typed either a completed index label, or a partial string.
  240.      Find an exact match, or, failing that, the first index entry containing
  241.      the partial string.  So, we just call info_next_index_match () with minor
  242.      manipulation of INDEX_OFFSET. */
  243.   {
  244.     int old_offset;
  245.  
  246.     /* Start the search right after/before this index. */
  247.     if (count < 0)
  248.       {
  249.     register int i;
  250.     for (i = 0; index_index[i]; i++);
  251.     index_offset = i;
  252.       }
  253.     else
  254.       index_offset = -1;
  255.  
  256.     old_offset == index_offset;
  257.  
  258.     /* The "last" string searched for is this one. */
  259.     index_search = line;
  260.  
  261.     /* Find it, or error. */
  262.     info_next_index_match (window, count, 0);
  263.  
  264.     /* If the search failed, return the index offset to where it belongs. */
  265.     if (index_offset == old_offset)
  266.       index_offset = 0;
  267.   }
  268. }
  269.  
  270. DECLARE_INFO_COMMAND (info_next_index_match,
  271.  "Go to the next matching index item from the last `\\[index-search]' command")
  272. {
  273.   register int i;
  274.   int partial, dir;
  275.   NODE *node;
  276.  
  277.   /* If there is no previous search string, the user hasn't built an index
  278.      yet. */
  279.   if (!index_search)
  280.     {
  281.       info_error ("No previous index search string.");
  282.       return;
  283.     }
  284.  
  285.   /* If there is no index, that is an error. */
  286.   if (!index_index)
  287.     {
  288.       info_error ("No index entries.");
  289.       return;
  290.     }
  291.  
  292.   /* The direction of this search is controlled by the value of the
  293.      numeric argument. */
  294.   if (count < 0)
  295.     dir = -1;
  296.   else
  297.     dir = 1;
  298.  
  299.   /* Search for the next occurence of index_search.  First try to find
  300.      an exact match. */
  301.   partial = 0;
  302.  
  303.   for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
  304.     if (strcmp (index_search, index_index[i]->label) == 0)
  305.       break;
  306.  
  307.   /* If that failed, look for the next substring match. */
  308.   if ((i < 0) || (!index_index[i]))
  309.     {
  310.       for (i = index_offset + dir; (i > -1) && (index_index[i]); i += dir)
  311.     if (string_in_line (index_search, index_index[i]->label) != -1)
  312.       break;
  313.  
  314.       if ((i > -1) && (index_index[i]))
  315.     partial = string_in_line (index_search, index_index[i]->label);
  316.     }
  317.  
  318.   /* If that failed, print an error. */
  319.   if ((i < 0) || (!index_index[i]))
  320.     {
  321.       info_error ("No %sindex entries containing \"%s\".",
  322.           index_offset > 0 ? "more " : "", index_search);
  323.       return;
  324.     }
  325.  
  326.   /* Okay, we found the next one.  Move the offset to the current entry. */
  327.   index_offset = i;
  328.  
  329.   /* Report to the user on what we have found. */
  330.   {
  331.     register int j;
  332.     char *name = "CAN'T SEE THIS";
  333.     char *match;
  334.  
  335.     for (j = 0; index_nodenames[j]; j++)
  336.       {
  337.     if ((i >= index_nodenames[j]->first) &&
  338.         (i <= index_nodenames[j]->last))
  339.       {
  340.         name = index_nodenames[j]->name;
  341.         break;
  342.       }
  343.       }
  344.  
  345.     /* If we had a partial match, indicate to the user which part of the
  346.        string matched. */
  347.     match = savestring (index_index[i]->label);
  348.  
  349.     if (partial && show_index_match)
  350.       {
  351.     int j, ls, start, upper;
  352.  
  353.     ls = strlen (index_search);
  354.     start = partial - ls;
  355.     upper = isupper (match[start]) ? 1 : 0;
  356.  
  357.     for (j = 0; j < ls; j++)
  358.       if (upper)
  359.         match[j + start] = info_tolower (match[j + start]);
  360.       else
  361.         match[j + start] = info_toupper (match[j + start]);
  362.       }
  363.  
  364.     {
  365.       char *format;
  366.  
  367.       format = replace_in_documentation
  368.     ("Found \"%s\" in %s. (`\\[next-index-match]' tries to find next.)");
  369.  
  370.       window_message_in_echo_area (format, match, name);
  371.     }
  372.  
  373.     free (match);
  374.   }
  375.  
  376.   /* Select the node corresponding to this index entry. */
  377.   node = info_get_node (index_index[i]->filename, index_index[i]->nodename);
  378.  
  379.   if (!node)
  380.     {
  381.       info_error (CANT_FILE_NODE,
  382.           index_index[i]->filename, index_index[i]->nodename);
  383.       return;
  384.     }
  385.  
  386.   set_remembered_pagetop_and_point (window);
  387.   window_set_node_of_window (window, node);
  388.   remember_window_and_node (window, node);
  389.  
  390.  
  391.   /* Try to find an occurence of LABEL in this node. */
  392.   {
  393.     long start, loc;
  394.  
  395.     start = window->line_starts[1] - window->node->contents;
  396.     loc = info_target_search_node (node, index_index[i]->label, start);
  397.  
  398.     if (loc != -1)
  399.       {
  400.     window->point = loc;
  401.     window_adjust_pagetop (window);
  402.       }
  403.   }
  404. }
  405.  
  406. /* **************************************************************** */
  407. /*                                    */
  408. /*           Info APROPOS: Search every known index.        */
  409. /*                                    */
  410. /* **************************************************************** */
  411.  
  412. /* For every menu item in DIR, search the indices of that file for
  413.    SEARCH_STRING. */
  414. REFERENCE **
  415. apropos_in_all_indices (search_string, inform)
  416.      char *search_string;
  417.      int inform;
  418. {
  419.   register int i, dir_index;
  420.   REFERENCE **all_indices = (REFERENCE **)NULL;
  421.   REFERENCE **dir_menu = (REFERENCE **)NULL;
  422.   NODE *dir_node;
  423.   int printed = 0;
  424.  
  425.   dir_node = info_get_node ("dir", "Top");
  426.   if (dir_node)
  427.     dir_menu = info_menu_of_node (dir_node);
  428.  
  429.   if (!dir_menu)
  430.     return;
  431.  
  432.   /* For every menu item in DIR, get the associated node's file buffer and
  433.      read the indices of that file buffer.  Gather all of the indices into
  434.      one large one. */
  435.   for (dir_index = 0; dir_menu[dir_index]; dir_index++)
  436.     {
  437.       REFERENCE **this_index, *this_item;
  438.       NODE *this_node;
  439.       FILE_BUFFER *this_fb;
  440.  
  441.       this_item = dir_menu[dir_index];
  442.  
  443.       if (!this_item->filename)
  444.     {
  445.       if (dir_node->parent)
  446.         this_item->filename = savestring (dir_node->parent);
  447.       else
  448.         this_item->filename = savestring (dir_node->filename);
  449.     }
  450.  
  451.       /* Find this node.  If we cannot find it, try using the label of the
  452.      entry as a file (i.e., "(LABEL)Top"). */
  453.       this_node = info_get_node (this_item->filename, this_item->nodename);
  454.  
  455.       if (!this_node && this_item->nodename &&
  456.       (strcmp (this_item->label, this_item->nodename) == 0))
  457.     this_node = info_get_node (this_item->label, "Top");
  458.  
  459.       if (!this_node)
  460.     continue;
  461.  
  462.       /* Get the file buffer associated with this node. */
  463.       {
  464.     char *files_name;
  465.  
  466.     files_name = this_node->parent;
  467.     if (!files_name)
  468.       files_name = this_node->filename;
  469.  
  470.     this_fb = info_find_file (files_name);
  471.  
  472.     if (this_fb && inform)
  473.       message_in_echo_area ("Scanning indices of \"%s\"...", files_name);
  474.  
  475.     this_index = info_indices_of_file_buffer (this_fb);
  476.     free (this_node);
  477.  
  478.     if (this_fb && inform)
  479.       unmessage_in_echo_area ();
  480.       }
  481.  
  482.       if (this_index)
  483.     {
  484.       /* Remember the filename which contains this set of references. */
  485.       for (i = 0; this_index && this_index[i]; i++)
  486.         if (!this_index[i]->filename)
  487.           this_index[i]->filename = savestring (this_fb->filename);
  488.  
  489.       /* Concatenate with the other indices.  */
  490.       all_indices = info_concatenate_references (all_indices, this_index);
  491.     }
  492.     }
  493.  
  494.   info_free_references (dir_menu);
  495.  
  496.   /* Build a list of the references which contain SEARCH_STRING. */
  497.   if (all_indices)
  498.     {
  499.       REFERENCE *entry, **apropos_list = (REFERENCE **)NULL;
  500.       int apropos_list_index = 0;
  501.       int apropos_list_slots = 0;
  502.  
  503.       for (i = 0; (entry = all_indices[i]); i++)
  504.     {
  505.       if (string_in_line (search_string, entry->label) != -1)
  506.         {
  507.           add_pointer_to_array
  508.         (entry, apropos_list_index, apropos_list, apropos_list_slots,
  509.          100, REFERENCE *);
  510.         }
  511.       else
  512.         {
  513.           maybe_free (entry->label);
  514.           maybe_free (entry->filename);
  515.           maybe_free (entry->nodename);
  516.           free (entry);
  517.         }
  518.     }
  519.  
  520.       free (all_indices);
  521.       all_indices = apropos_list;
  522.     }
  523.   return (all_indices);
  524. }
  525.  
  526. #define APROPOS_NONE \
  527.    "No available info files reference \"%s\" in their indices."
  528.  
  529. void
  530. info_apropos (string)
  531.      char *string;
  532. {
  533.   REFERENCE **apropos_list;
  534.  
  535.   apropos_list = apropos_in_all_indices (string, 0);
  536.  
  537.   if (!apropos_list)
  538.     {
  539.       info_error (APROPOS_NONE, string);
  540.     }
  541.   else
  542.     {
  543.       register int i;
  544.       REFERENCE *entry;
  545.  
  546.       for (i = 0; (entry = apropos_list[i]); i++)
  547.     fprintf (stderr, "\"(%s)%s\" -- %s\n",
  548.          entry->filename, entry->nodename, entry->label);
  549.     }
  550.   info_free_references (apropos_list);
  551. }
  552.  
  553. static char *apropos_list_nodename = "*Apropos*";
  554.  
  555. DECLARE_INFO_COMMAND (info_index_apropos,
  556.    "Grovel all known info file's indices for a string and build a menu")
  557. {
  558.   char *line;
  559.  
  560.   line = info_read_in_echo_area (window, "Index apropos: ");
  561.  
  562.   window = active_window;
  563.  
  564.   /* User aborted? */
  565.   if (!line)
  566.     {
  567.       info_abort_key (window, 1, 1);
  568.       return;
  569.     }
  570.  
  571.   /* User typed something? */
  572.   if (*line)
  573.     {
  574.       REFERENCE **apropos_list;
  575.       NODE *apropos_node;
  576.  
  577.       apropos_list = apropos_in_all_indices (line, 1);
  578.  
  579.       if (!apropos_list)
  580.     {
  581.       info_error (APROPOS_NONE, line);
  582.     }
  583.       else
  584.     {
  585.       register int i;
  586.       char *line_buffer;
  587.  
  588.       initialize_message_buffer ();
  589.       printf_to_message_buffer
  590.         ("\n* Menu: Nodes whoses indices contain \"%s\":\n", line);
  591.       line_buffer = (char *)xmalloc (500);
  592.  
  593.       for (i = 0; apropos_list[i]; i++)
  594.         {
  595.           int len;
  596.           sprintf (line_buffer, "* (%s)%s::",
  597.                apropos_list[i]->filename, apropos_list[i]->nodename);
  598.           len = pad_to (36, line_buffer);
  599.           sprintf (line_buffer + len, "%s", apropos_list[i]->label);
  600.           printf_to_message_buffer ("%s\n", line_buffer);
  601.         }
  602.       free (line_buffer);
  603.     }
  604.  
  605.       apropos_node = message_buffer_to_node ();
  606.       add_gcable_pointer (apropos_node);
  607.       name_internal_node (apropos_node, apropos_list_nodename);
  608.  
  609.       /* Even though this is an internal node, we don't want the window
  610.      system to treat it specially.  So we turn off the internalness
  611.      of it here. */
  612.       apropos_node->flags &= ~N_IsInternal;
  613.  
  614.       /* Find/Create a window to contain this node. */
  615.       {
  616.     WINDOW *new;
  617.     NODE *node;
  618.  
  619.     set_remembered_pagetop_and_point (window);
  620.  
  621.     /* If a window is visible and showing an apropos list already,
  622.        re-use it. */
  623.     for (new = windows; new; new = new->next)
  624.       {
  625.         node = new->node;
  626.  
  627.         if (internal_info_node_p (node) &&
  628.         (strcmp (node->nodename, apropos_list_nodename) == 0))
  629.           break;
  630.       }
  631.  
  632.     /* If we couldn't find an existing window, try to use the next window
  633.        in the chain. */
  634.     if (!new && window->next)
  635.       new = window->next;
  636.  
  637.     /* If we still don't have a window, make a new one to contain
  638.        the list. */
  639.     if (!new)
  640.       {
  641.         WINDOW *old_active;
  642.  
  643.         old_active = active_window;
  644.         active_window = window;
  645.         new = window_make_window ((NODE *)NULL);
  646.         active_window = old_active;
  647.       }
  648.  
  649.     /* If we couldn't make a new window, use this one. */
  650.     if (!new)
  651.       new = window;
  652.  
  653.     /* Lines do not wrap in this window. */
  654.     new->flags |= W_NoWrap;
  655.  
  656.     window_set_node_of_window (new, apropos_node);
  657.     remember_window_and_node (new, apropos_node);
  658.     active_window = new;
  659.       }
  660.       info_free_references (apropos_list);
  661.     }
  662.   free (line);
  663.  
  664.   if (!info_error_was_printed)
  665.     window_clear_echo_area ();
  666. }
  667.  
  668.